﻿using System;
using System.Security;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace PickGold.Api
{
	/// <summary/>
	[SuppressUnmanagedCodeSecurity]
	internal class WinUsb
	{
		/// <summary/>
		public const string DLL = ApiUtil.DLL_WINUSB;


		[DllImport(DLL, EntryPoint = "WinUsb_AbortPipe", SetLastError = true)]
		public static extern bool AbortPipe([In] SafeHandle InterfaceHandle, byte PipeID);

		[DllImport(DLL, EntryPoint = "WinUsb_ControlTransfer", SetLastError = true)]
		public static extern bool ControlTransfer([In] SafeHandle InterfaceHandle,
														  [In] UsbSetupPacket SetupPacket,
														  IntPtr Buffer,
														  int BufferLength,
														  out int LengthTransferred,
														  IntPtr pOVERLAPPED);

		[DllImport(DLL, EntryPoint = "WinUsb_FlushPipe", SetLastError = true)]
		public static extern bool FlushPipe([In] SafeHandle InterfaceHandle, byte PipeID);

		[DllImport(DLL, EntryPoint = "WinUsb_Free", SetLastError = true)]
		public static extern bool Free([In] IntPtr InterfaceHandle);

		[DllImport(DLL, EntryPoint = "WinUsb_GetAssociatedInterface", SetLastError = true)]
		public static extern bool GetAssociatedInterface([In] SafeHandle InterfaceHandle,
																  byte AssociatedInterfaceIndex,
																  ref IntPtr AssociatedInterfaceHandle);

		[DllImport(DLL, EntryPoint = "WinUsb_GetCurrentAlternateSetting", SetLastError = true)]
		public static extern bool GetCurrentAlternateSetting([In] SafeHandle InterfaceHandle, out byte SettingNumber);


		[DllImport(DLL, EntryPoint = "WinUsb_GetDescriptor", SetLastError = true)]
		public static extern bool GetDescriptor([In] SafeHandle InterfaceHandle,
														byte DescriptorType,
														byte Index,
														ushort LanguageID,
														IntPtr Buffer,
														int BufferLength,
														out int LengthTransferred);

		[DllImport(DLL, EntryPoint = "WinUsb_GetOverlappedResult", SetLastError = true)]
		public static extern bool GetOverlappedResult([In] SafeHandle InterfaceHandle,
															  IntPtr pOVERLAPPED,
															  out int lpNumberOfBytesTransferred,
															  bool Wait);

		[DllImport(DLL, EntryPoint = "WinUsb_GetPipePolicy", SetLastError = true)]
		public static extern bool GetPipePolicy([In] SafeHandle InterfaceHandle,
														 byte PipeID,
														 PipePolicyType policyType,
														 ref int ValueLength,
														 IntPtr Value);

		[DllImport(DLL, EntryPoint = "WinUsb_GetPowerPolicy", SetLastError = true)]
		public static extern bool GetPowerPolicy([In] SafeHandle InterfaceHandle,
														  PowerPolicyType policyType,
														  ref int ValueLength,
														  IntPtr Value);

		[DllImport(DLL, EntryPoint = "WinUsb_Initialize", SetLastError = true)]
		public static extern bool Initialize([In] SafeHandle DeviceHandle, [Out, In] ref SafeWinUsbInterfaceHandle InterfaceHandle);

		[DllImport(DLL, EntryPoint = "WinUsb_QueryDeviceInformation", SetLastError = true)]
		public static extern bool QueryDeviceInformation([In] SafeHandle InterfaceHandle,
																  DeviceInformationTypes InformationType,
																  ref int BufferLength,
																  [MarshalAs(UnmanagedType.AsAny), In, Out] object Buffer);

		[DllImport(DLL, EntryPoint = "WinUsb_QueryInterfaceSettings", SetLastError = true)]
		public static extern bool QueryInterfaceSettings([In] SafeHandle InterfaceHandle,
																  byte AlternateInterfaceNumber,
																  [MarshalAs(UnmanagedType.LPStruct), In, Out] UsbInterfaceDescriptor
																	  UsbAltInterfaceDescriptor);

		[DllImport(DLL, EntryPoint = "WinUsb_QueryPipe", SetLastError = true)]
		public static extern bool QueryPipe([In] SafeHandle InterfaceHandle,
													 byte AlternateInterfaceNumber,
													 byte PipeIndex,
													 [MarshalAs(UnmanagedType.LPStruct), In, Out] PipeInformation PipeInformation);

		[DllImport(DLL, EntryPoint = "WinUsb_ReadPipe", SetLastError = true)]
		public static extern bool ReadPipe([In] SafeHandle InterfaceHandle,
												   byte PipeID,
												   Byte[] Buffer,
												   int BufferLength,
												   out int LengthTransferred,
												   IntPtr pOVERLAPPED);

		[DllImport(DLL, EntryPoint = "WinUsb_ReadPipe", SetLastError = true)]
		public static extern bool ReadPipe([In] SafeHandle InterfaceHandle,
												   byte PipeID,
												   IntPtr pBuffer,
												   int BufferLength,
												   out int LengthTransferred,
												   IntPtr pOVERLAPPED);

		[DllImport(DLL, EntryPoint = "WinUsb_ResetPipe", SetLastError = true)]
		public static extern bool ResetPipe([In] SafeHandle InterfaceHandle, byte PipeID);

		[DllImport(DLL, EntryPoint = "WinUsb_SetPipePolicy", SetLastError = true)]
		public static extern bool SetPipePolicy([In] SafeHandle InterfaceHandle,
														 byte PipeID,
														 PipePolicyType policyType,
														 int ValueLength,
														 IntPtr Value);

		[DllImport(DLL, EntryPoint = "WinUsb_SetPowerPolicy", SetLastError = true)]
		public static extern bool SetPowerPolicy([In] SafeHandle InterfaceHandle, PowerPolicyType policyType, int ValueLength, IntPtr Value);

		[DllImport(DLL, EntryPoint = "WinUsb_WritePipe", SetLastError = true)]
		public static extern bool WritePipe([In] SafeHandle InterfaceHandle,
													byte PipeID,
													Byte[] Buffer,
													int BufferLength,
													out int LengthTransferred,
													IntPtr pOVERLAPPED);

		[DllImport(DLL, EntryPoint = "WinUsb_WritePipe", SetLastError = true)]
		public static extern bool WritePipe([In] SafeHandle InterfaceHandle,
													byte PipeID,
													IntPtr pBuffer,
													int BufferLength,
													out int LengthTransferred,
													IntPtr pOVERLAPPED);
	}

	/// <summary> Transfers data to the main control endpoint (Endpoint 0).
	/// </summary> 
	/// <remarks> All USB devices respond to requests from the host on the device抯 Default Control Pipe. These requests are made using control transfers. The request and the request抯 parameters are sent to the device in the Setup packet. The host is responsible for establishing the values passed in the fields. Every Setup packet has eight bytes.
	/// </remarks> 
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public struct UsbSetupPacket
	{
		/// <summary>
		/// This bitmapped field identifies the characteristics of the specific request. In particular, this field identifies the direction of data transfer in the second phase of the control transfer. The state of the Direction bit is ignored if the wLength field is zero, signifying there is no Data stage.
		/// The USB Specification defines a series of standard requests that all devices must support. In addition, a device class may define additional requests. A device vendor may also define requests supported by the device.
		/// Requests may be directed to the device, an interface on the device, or a specific endpoint on a device. This field also specifies the intended recipient of the request. When an interface or endpoint is specified, the wIndex field identifies the interface or endpoint.
		/// </summary>
		/// <remarks>
		/// <ul>Characteristics of request:
		/// <li>D7: Data transfer direction</li>
		/// <li>0 = Host-to-device</li>
		/// <li>1 = Device-to-host</li>
		/// <li>D6...5: Type</li>
		/// <li>0 = Standard</li>
		/// <li>1 = Class</li>
		/// <li>2 = Vendor</li>
		/// <li>3 = Reserved</li>
		/// <li>D4...0: Recipient</li>
		/// <li>0 = Device</li>
		/// <li>1 = Interface</li>
		/// <li>2 = Endpoint</li>
		/// <li>3 = Other</li>
		/// <li>4...31 = Reserved</li>
		/// </ul>
		/// </remarks>
		public byte RequestType;

		/// <summary>
		/// This field specifies the particular request. The Type bits in the bmRequestType field modify the meaning of this field. This specification defines values for the bRequest field only when the bits are reset to zero, indicating a standard request.
		/// </summary>
		public byte Request;

		/// <summary>
		/// The contents of this field vary according to the request. It is used to pass a parameter to the device, specific to the request.
		/// </summary>
		public short Value;

		/// <summary>
		/// The contents of this field vary according to the request. It is used to pass a parameter to the device, specific to the request.
		/// </summary>
		public short Index;

		/// <summary>
		/// This field specifies the length of the data transferred during the second phase of the control transfer. The direction of data transfer (host-to-device or device-to-host) is indicated by the Direction bit of the <see cref="RequestType"/> field. If this field is zero, there is no data transfer phase. On an input request, a device must never return more data than is indicated by the wLength value; it may return less. On an output request, wLength will always indicate the exact amount of data to be sent by the host. Device behavior is undefined if the host should send more data than is specified in wLength.
		/// </summary>
		public short Length;

		/// <summary>
		/// Creates a new instance of a <see cref="UsbSetupPacket"/> and initializes all the fields with the following parameters.
		/// </summary>
		/// <param name="requestType">See <see cref="UsbSetupPacket.RequestType"/>.</param>
		/// <param name="request">See <see cref="UsbSetupPacket.Request"/>.</param>
		/// <param name="value">See <see cref="UsbSetupPacket.Value"/>.</param>
		/// <param name="index">See <see cref="UsbSetupPacket.Index"/>.</param>
		/// <param name="length">See <see cref="UsbSetupPacket.Length"/>.</param>
		public UsbSetupPacket(byte requestType, byte request, short value, short index, short length)
		{
			RequestType = requestType;
			Request = request;
			Value = value;
			Index = index;
			Length = length;
		}
	}

	public enum PipePolicyType : byte
	{
		ShortPacketTerminate = 0x01,
		AutoClearStall = 0x02,
		PipeTransferTimeout = 0x03,
		IgnoreShortPackets = 0x04,
		AllowPartialReads = 0x05,
		AutoFlush = 0x06,
		RawIo = 0x07,
		MaximumTransferSize = 0x08,
	}

	public enum PowerPolicyType : byte
	{
		AutoSuspend = 0x81,
		EnableWake = 0x82,
		SuspendDelay = 0x83,
	}

	/// <summary> Types of information that can be retrieved with the WinUsb QueryDevice function.
	/// </summary> 
	public enum DeviceInformationTypes : byte
	{
		/// <summary>
		/// The device speed.
		/// </summary>
		DeviceSpeed = 0x01
	}

	/// <summary> Standard USB descriptor types.
	/// </summary> 
	[Flags]
	public enum DescriptorType : byte
	{
		/// <summary>
		/// Device descriptor type.
		/// </summary>
		Device = 1,
		/// <summary>
		/// Configuration descriptor type.
		/// </summary>
		Configuration = 2,
		/// <summary>
		/// String descriptor type.
		/// </summary>
		String = 3,
		/// <summary>
		/// Interface descriptor type.
		/// </summary>
		Interface = 4,
		/// <summary>
		/// Endpoint descriptor type.
		/// </summary>
		Endpoint = 5,
		/// <summary>
		/// Device Qualifier descriptor type.
		/// </summary>
		DeviceQualifier = 6,
		/// <summary>
		/// Other Speed Configuration descriptor type.
		/// </summary>
		OtherSpeedConfiguration = 7,
		/// <summary>
		/// Interface Power descriptor type.
		/// </summary>
		InterfacePower = 8,
		/// <summary>
		/// OTG descriptor type.
		/// </summary>
		OTG = 9,
		/// <summary>
		/// Debug descriptor type.
		/// </summary>
		Debug = 10,
		/// <summary>
		/// Interface Association descriptor type.
		/// </summary>
		InterfaceAssociation = 11,

		///<summary> HID descriptor</summary>
		Hid = 0x21,

		///<summary> HID report descriptor</summary>
		HidReport = 0x22,

		///<summary> Physical descriptor</summary>
		Physical = 0x23,

		///<summary> Hub descriptor</summary>
		Hub = 0x29
	}

	/// <summary> Base class for all usb descriptors structures.
	/// </summary> 
	/// <remarks> This is the actual descriptor as described in the USB 2.0 Specifications.
	/// </remarks> 
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public abstract class UsbDescriptor
	{
		/// <summary>
		/// String value used to seperate the name/value pairs for all ToString overloads of the descriptor classes.
		/// </summary>
		public static string ToStringParamValueSeperator = ":";

		/// <summary>
		/// String value used to seperate the name/value groups for all ToString overloads of the descriptor classes.
		/// </summary>
		public static string ToStringFieldSeperator = "\r\n";

		/// <summary>
		/// Total size of this structure in bytes.
		/// </summary>
		public static readonly int Size = Marshal.SizeOf(typeof(UsbDescriptor));

		/// <summary>
		/// Length of structure reported by the associated usb device.
		/// </summary>
		public byte Length;

		/// <summary>
		/// Type of structure reported by the associated usb device.
		/// </summary>
		public DescriptorType DescriptorType;
	}

	public class SafeWinUsbInterfaceHandle : SafeHandle
	{
		public SafeWinUsbInterfaceHandle() : base(IntPtr.Zero, true) { }

		public SafeWinUsbInterfaceHandle(IntPtr handle) : base(handle, true) { }

		///<summary>
		///Gets a value indicating whether the <see cref="SafeWinUsbInterfaceHandle"/> value is invalid.
		///</summary>
		///
		///<returns>
		///true if the <see cref="SafeWinUsbInterfaceHandle"/> is valid; otherwise, false.
		///</returns>
		public override bool IsInvalid
		{
			get
			{
				return (handle == IntPtr.Zero || handle.ToInt64() == -1);
			}
		}

		///<summary>
		///Executes the code required to free the <see cref="SafeWinUsbInterfaceHandle"/>.
		///</summary>
		///
		///<returns>
		///true if the <see cref="SafeWinUsbInterfaceHandle"/> is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a ReleaseHandleFailed Managed Debugging Assistant.
		///</returns>
		///
		protected override bool ReleaseHandle()
		{
			var bSuccess = true;
			if (IsInvalid)
				return true;

			var v = WinUsb.Free(handle);
			handle = IntPtr.Zero;
			return v;
		}
	}

	///<summary> Device and/or Interface Class codes</summary>
	[Flags]
	public enum ClassCodeType : byte
	{
		///<summary>In the context of a "device descriptor", this bDeviceClass value indicates that each interface specifies its own class information and all interfaces operate independently.</summary>
		PerInterface = 0,

		///<summary>Audio class</summary>
		Audio = 1,

		///<summary> Communications class</summary>
		Comm = 2,

		///<summary> Human Interface Device class</summary>
		Hid = 3,

		///<summary> Printer dclass</summary>
		Printer = 7,

		///<summary> Picture transfer protocol class</summary>
		Ptp = 6,

		///<summary> Mass storage class</summary>
		MassStorage = 8,

		///<summary> Hub class</summary>
		Hub = 9,

		///<summary> Data class</summary>
		Data = 10,

		///<summary> Class is vendor-specific</summary>
		VendorSpec = 0xff
	}

	/// <summary>
	/// A structure representing the standard USB endpoint descriptor. This
	/// descriptor is documented in section 9.6.3 of the USB 2.0 specification.
	/// All multiple-byte fields are represented in host-endian format.
	/// </summary>
	[StructLayout(LayoutKind.Sequential, Pack = 0)]//LIBUSB_PACK = 0
	public class MonoUsbEndpointDescriptor
	{
		///<summary> Size of this descriptor (in bytes)</summary>
		public readonly Byte bLength;

		///<summary> Descriptor type. Will have value LIBUSB_DT_ENDPOINT in this context.</summary>
		public readonly DescriptorType bDescriptorType;

		///<summary> The address of the endpoint described by this descriptor. Bits 0:3 are the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, see \ref libusb_endpoint_direction.</summary>
		public readonly Byte bEndpointAddress;

		///<summary> Attributes which apply to the endpoint when it is configured using the bConfigurationValue. Bits 0:1 determine the transfer type and correspond to \ref libusb_transfer_type. Bits 2:3 are only used for isochronous endpoints and correspond to \ref libusb_iso_sync_type. Bits 4:5 are also only used for isochronous endpoints and correspond to \ref libusb_iso_usage_type. Bits 6:7 are reserved.</summary>
		public readonly Byte bmAttributes;

		///<summary> Maximum packet size this endpoint is capable of sending/receiving.</summary>
		public readonly short wMaxPacketSize;

		///<summary> Interval for polling endpoint for data transfers.</summary>
		public readonly Byte bInterval;

		///<summary> For audio devices only: the rate at which synchronization feedback is provided.</summary>
		public readonly Byte bRefresh;

		///<summary> For audio devices only: the address if the synch endpoint</summary>
		public readonly Byte bSynchAddress;

		///<summary> Extra descriptors. If libusb encounters unknown endpoint descriptors, it will store them here, should you wish to parse them.</summary>
		private readonly IntPtr pExtraBytes;

		///<summary> Length of the extra descriptors, in bytes.</summary>
		public readonly int ExtraLength;

		///<summary> Extra descriptors. If libusb encounters unknown endpoint descriptors, it will store them here, should you wish to parse them.</summary>
		public byte[] ExtraBytes
		{
			get
			{
				byte[] bytes = new byte[ExtraLength];
				Marshal.Copy(pExtraBytes, bytes, 0, bytes.Length);
				return bytes;
			}
		}
	}

	/// <summary>
	/// A structure representing the standard USB interface descriptor. This
	/// descriptor is documented in section 9.6.5 of the USB 2.0 specification.
	/// All multiple-byte fields are represented in host-endian format.
	/// </summary>
	[StructLayout(LayoutKind.Sequential, Pack = 0)]//LIBUSB_PACK = 0
	public class MonoUsbAltInterfaceDescriptor
	{
		///<summary>Size of this descriptor (in bytes)</summary>
		public readonly Byte bLength;

		///<summary>Descriptor type. Will have value LIBUSB_DT_INTERFACE in this context.</summary>
		public readonly DescriptorType bDescriptorType;

		///<summary>Number of this interface</summary>
		public readonly Byte bInterfaceNumber;

		///<summary>Value used to select this alternate setting for this interface</summary>
		public readonly Byte bAlternateSetting;

		///<summary> Number of endpoints used by this interface (excluding the control endpoint).</summary>
		public readonly Byte bNumEndpoints;

		///<summary> USB-IF class code for this interface. See ClassCodeType.</summary>
		public readonly ClassCodeType bInterfaceClass;

		///<summary> USB-IF subclass code for this interface, qualified by the bInterfaceClass value</summary>
		public readonly Byte bInterfaceSubClass;

		///<summary> USB-IF protocol code for this interface, qualified by the bInterfaceClass and bInterfaceSubClass values</summary>
		public readonly Byte bInterfaceProtocol;

		///<summary> Index of string descriptor describing this interface</summary>
		public readonly Byte iInterface;

		///<summary> Array of endpoint descriptors. This length of this array is determined by the bNumEndpoints field.</summary>
		private readonly IntPtr pEndpointDescriptors;

		///<summary> Extra descriptors. If libusb encounters unknown interface descriptors, it will store them here, should you wish to parse them.</summary>
		private IntPtr pExtraBytes;

		///<summary> Length of the extra descriptors, in bytes.</summary>
		public readonly int ExtraLength;

		///<summary> Extra descriptors. If libusb encounters unknown interface descriptors, it will store them here, should you wish to parse them.</summary>
		public byte[] ExtraBytes
		{
			get
			{
				byte[] bytes = new byte[ExtraLength];
				Marshal.Copy(pExtraBytes, bytes, 0, bytes.Length);
				return bytes;
			}
		}

		///<summary> Array of endpoint descriptors. This length of this array is determined by the bNumEndpoints field.</summary>
		public List<MonoUsbEndpointDescriptor> EndpointList
		{
			get
			{
				List<MonoUsbEndpointDescriptor> endpointList = new List<MonoUsbEndpointDescriptor>();
				int iEndpoint;
				for (iEndpoint = 0; iEndpoint < bNumEndpoints; iEndpoint++)
				{
					IntPtr pNextInterface = new IntPtr(pEndpointDescriptors.ToInt64() + (Marshal.SizeOf(typeof(MonoUsbEndpointDescriptor)) * iEndpoint));
					MonoUsbEndpointDescriptor monoUsbEndpoint = new MonoUsbEndpointDescriptor();
					Marshal.PtrToStructure(pNextInterface, monoUsbEndpoint);
					endpointList.Add(monoUsbEndpoint);
				}

				return endpointList;
			}
		}
	}

	/// <summary> Usb Interface Descriptor.
	/// </summary> 
	/// <remarks> This is the actual descriptor as described in the USB 2.0 Specifications.
	/// </remarks> 
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public class UsbInterfaceDescriptor : UsbDescriptor
	{
		/// <summary>
		/// Total size of this structure in bytes.
		/// </summary>
		public new static readonly int Size = Marshal.SizeOf(typeof(UsbInterfaceDescriptor));

		/// <summary>
		/// Number of Interface
		/// </summary>
		public readonly byte InterfaceID;

		/// <summary>
		/// Value used to select alternative setting
		/// </summary>
		public readonly byte AlternateID;

		/// <summary>
		/// Number of Endpoints used for this interface
		/// </summary>
		public readonly byte EndpointCount;

		/// <summary>
		/// Class Code (Assigned by USB Org)
		/// </summary>
		public readonly ClassCodeType Class;

		/// <summary>
		/// Subclass Code (Assigned by USB Org)
		/// </summary>
		public readonly byte SubClass;

		/// <summary>
		/// Protocol Code (Assigned by USB Org)
		/// </summary>
		public readonly byte Protocol;

		/// <summary>
		/// Index of String Descriptor Describing this interface
		/// </summary>
		public readonly byte StringIndex;

		internal UsbInterfaceDescriptor() { }

		internal UsbInterfaceDescriptor(MonoUsbAltInterfaceDescriptor altInterfaceDescriptor)
		{
			AlternateID = altInterfaceDescriptor.bAlternateSetting;
			Class = altInterfaceDescriptor.bInterfaceClass;
			DescriptorType = altInterfaceDescriptor.bDescriptorType;
			EndpointCount = altInterfaceDescriptor.bNumEndpoints;
			InterfaceID = altInterfaceDescriptor.bInterfaceNumber;
			Length = altInterfaceDescriptor.bLength;
			Protocol = altInterfaceDescriptor.bInterfaceProtocol;
			StringIndex = altInterfaceDescriptor.iInterface;
			SubClass = altInterfaceDescriptor.bInterfaceSubClass;
		}
	}

	/// <summary> All possible USB endpoint types.
	/// </summary> 
	[Flags]
	public enum EndpointType : byte
	{
		/// <summary>
		/// Control endpoint type.
		/// </summary>
		Control,
		/// <summary>
		/// Isochronous endpoint type.
		/// </summary>
		Isochronous,
		/// <summary>
		/// Bulk endpoint type.
		/// </summary>
		Bulk,
		/// <summary>
		/// Interrupt endpoint type.
		/// </summary>
		Interrupt
	}

	/// <summary> WinUsb Pipe information.
	/// </summary> 
	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	public class PipeInformation
	{
		/// <summary>
		/// Size of the structure in bytes.
		/// </summary>
		public static readonly int Size = Marshal.SizeOf(typeof(PipeInformation));

		/// <summary>
		/// Specifies the pipe type.
		/// </summary>
		public EndpointType PipeType;

		/// <summary>
		/// The pipe identifier (ID). 
		/// </summary>
		public byte PipeId;

		/// <summary>
		/// The maximum size, in bytes, of the packets that are transmitted on the pipe.
		/// </summary>
		public short MaximumPacketSize;

		/// <summary>
		/// The pipe interval.
		/// </summary>
		public byte Interval;
	}
}